package de.fhd.pms.dao;

import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import de.fhd.pms.Rechnung;
import de.fhd.pms.dto.FahrtTeilnehmerDTO;
import de.fhd.pms.dto.RechnungDTO;
import de.fhd.pms.model.Automobil;
import de.fhd.pms.model.Fahrt;
import de.fhd.pms.model.Teilnehmer;
//DOKUMENTATION FERTIG
/**
 * Dao Klasse für den Zugriff auf Fahrten.
 * <p>
 * Die Klasse FahrtDao wird verwendet, um Persistenz bzw. den Zugriff auf eine Datenbank zu
 * gewährleisten, ohne spezielle datenbankabhängige Befehle (z.B. Oracle, MySQL) verwenden zu müssen.
 * 
 * @author Martin Schmid
 * @author Florian Schachner
 */
public class FahrtDao extends HibernateDaoSupport {

	private static Logger log = Logger.getLogger(FahrtDao.class);

	SimpleDateFormat deutschesDatumsFormat = new SimpleDateFormat("dd.MM.yyyy kk:mm");

	/**
	 * Findet alle Fahrten im CarSharing-Projekt.<br/>
	 * Diese Methode findet alle beendeten und offenen Fahrten, 
	 * die im CarSharing-Projekt absolviert wurden und gibt diese Fahrten zurück.
	 * 
	 * @return alleFahrten - {@link List}<{@link Fahrt}>
	 * @author Martin Schmid
	 * @author Florian Schachner
	 */
	@SuppressWarnings("unchecked")
	public List<Fahrt> findAll() {
		HibernateTemplate template = getHibernateTemplate();
		@SuppressWarnings("rawtypes")
		List alleFahrten = template.loadAll(Fahrt.class);

		if (alleFahrten.isEmpty()) {
			log.info("Es wurden keine Fahrten gefunden");
		} else {
			log.info("Es wurden Fahrten gefunden");
		}

		return (List<Fahrt>) alleFahrten;
	}

	
	/**
	 * Findet eine spezifische {@link Fahrt} in der Datenbank.<br/>
	 * Diese Methode findet einen spezifischen Datensatz in der Fahrt-Tabelle der Datenbank
	 * anhand der übergebenen Identifizierungsnummer (ID) einer Fahrt und liefert
	 * das gefundene {@link Fahrt}-Objekt zurück.
	 * 
	 * @param id {@link Integer} - Primärschlüssel des Fahrt-Objekts
	 * @return fahrt {@link Fahrt}
	 * @author Martin Schmid
	 * @author Florian Schachner
	 */
	public Fahrt findById(int id) {
		log.info("FAHRTDAO: findById()");
		HibernateTemplate template = getHibernateTemplate();
		return (Fahrt) template.get(Fahrt.class, id);
	}

	
	
	/**
	 * Speichert eine spezifische {@link Fahrt} in der Datenbank.<br/>
	 * Diese Methode selektiert zunächst ein spezielles Automobil anhand dessen ID aus der Datenbank.
	 * Außerdem wird ein spezieller Teilnehmer aus der Datenbank selektiert.
	 * Im weiteren Verlauf wird eine neues Fahrt-Objekt erzeugt und mit allen
	 * relevaten Daten gefüllt.
	 * Das Attribut available wird bei Teilnehmer und Automobil auf false gesetzt,
	 * sodass das Automobil bzw. der Teilnehmer nicht mehr für eine weitere Fahrt 
	 * verwendet werden kann.
	 * Abschließend werden alle Objekte in der Datenbank geupdatet bzw. angelegt.
	 * 
	 * @param automobilId {@link Integer}
	 * @param teilnehmerId {@link Integer}
	 * @throws DaoException {@link DaoException}
	 * @author Martin Schmid
	 * @author Florian Schachner
	 */
	public void beginne(int automobilId, int teilnehmerId) throws DaoException {
		HibernateTemplate template = getHibernateTemplate();
		Automobil a = (Automobil) template.get(Automobil.class, automobilId);
		Teilnehmer t = (Teilnehmer) template.get(Teilnehmer.class, teilnehmerId);
			
		Fahrt f = new Fahrt();
		f.setAutomobil(a);
		f.setTeilnehmer(t);
		f.setStartkilometer(a.getKilometerstand());
		f.setStartstandort(a.getAktuellerstandort());
		a.setAvailable(false);
		t.setAvailable(false);
		
		if (t.getFahrerlaubnis() != true) {
			throw new DaoException("Der Teilnehmer besitzt keine Fahrerlaubnis und kann somit keine Fahrt antreten.");
		}

		try {
			template.saveOrUpdate(f);
			template.saveOrUpdate(a);
			template.saveOrUpdate(t);
		} catch (DataAccessException e) {
			throw new DaoException("Ein und der selbe Teilnehmer kann nicht zwei Fahrten gleichzeitig absolvieren.");
		}
	}

	
	/**
	 * Gibt eine aufbereitete Liste aller Fahrten zurück.</p>
	 * <p>
	 * Diese Methode liefert, mithilfe eines HibernateCallbacks, alle absolvierten Fahrten zurück. 
	 * Wird verwendet für die Anzeige aller Fahrten bzw. für die Anzeige um eine Fahrt zu beenden.
	 * </p>
	 * 
	 * @return {@link Set}<{@link FahrtTeilnehmerDTO}> - Sortierte Liste von FahrtPersonenDTO-Objekten.
	 * @see http://static.springframework.org/spring/docs/2.5.x/reference/orm.html#orm-hibernate-template
	 * @author Martin Schmid
	 * @author Florian Schachner
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Set<FahrtTeilnehmerDTO> findAllDTO() {
		HibernateTemplate template = getHibernateTemplate();
		return (Set<FahrtTeilnehmerDTO>) template
				.execute(new HibernateCallback() {
					@Override
					public Object doInHibernate(Session session)
							throws HibernateException, SQLException {
						//Criteria criteria = session.createCriteria(Fahrt.class).addOrder(Order.desc("startfahrt")); //Sortierung funktioniert nicht
						Criteria criteria = session.createCriteria(Fahrt.class);
						List<Fahrt> alleFahrten = criteria.list();
						return createDtoSet(alleFahrten);
					}
				});
	}

	
	/**
	 * Aufbereitung der Ausgabe der der DTO-Objekte.</p>
	 * <p>
	 * Diese Methode bereitet Daten für die Ausgabe der FahrtTeilnehmerDTO auf und liefert diese an die 
	 * Methode findAllDTO() zurück. 
	 * Wird verwendet für die Anzeige aller Fahrten bzw. für die Anzeige um eine Fahrt zu beenden.
	 * </p>
	 * 
	 * @return {@link Set}<{@link FahrtTeilnehmerDTO}> - Sortierte Liste von FahrtPersonenDTO-Objekten.
	 * @author Martin Schmid
	 * @author Florian Schachner
	 */
	private Set<FahrtTeilnehmerDTO> createDtoSet(List<Fahrt> fahrten) {
		Set<FahrtTeilnehmerDTO> results = new TreeSet<FahrtTeilnehmerDTO>();
		for (Fahrt f : fahrten) {
			Date a = f.getEndefahrt();
			results.add(new FahrtTeilnehmerDTO(f.getStartfahrt(), f.getId(), f
					.getAutomobil().getKennzeichen(), deutschesDatumsFormat
					.format(f.getStartfahrt()), (a == null ? ""
					: deutschesDatumsFormat.format(a)), (f.getTeilnehmer()
					.getNachname() + " " + f.getTeilnehmer().getVorname())));
		}
		return results;
	}

	
	/**
	 * Beendet eine spezifische {@link Fahrt}.<br/>
	 * Diese Methode selektiert zunächst ein spezielles Automobil, ein speziellen Teilnehmer 
	 * und eine spezielle Fahrt anhand dessen ID aus der Datenbank.
	 * 
	 * Im Fahrt-Objekt werden EndeKilometer, EndeStandort und der Zeitstempel des Fahrtendes gesetzt.
	 * Außerdem wird ein spezieller Teilnehmer aus der Datenbank selektiert.
	 * Das Attribut available wird bei Teilnehmer und Automobil auf true gesetzt,
	 * sodass das Automobil bzw. der Teilnehmer wieder für eine neue Fahrt zur Verfügung stehen.
	 * Abschließend werden alle Objekte in der Datenbank geupdatet.
	 * 
	 * @param fahrtID {@link Integer}
	 * @param standortID {@link Integer}
	 * @param endekilometer {@link Integer}
	 * @author Martin Schmid
	 * @author Florian Schachner
	 */
	public void beende(int fahrtID, int standortID, int endekilometer) {
		HibernateTemplate template = getHibernateTemplate();
		Fahrt f = (Fahrt) template.get(Fahrt.class, fahrtID);
		Automobil a = (Automobil) template.get(Automobil.class, f.getAutomobil().getId());
		Teilnehmer t = (Teilnehmer) template.get(Teilnehmer.class, f.getTeilnehmer().getId());
		
		f.setEndefahrt(new Date());
		f.setEndestandort(standortID);
		f.setEndekilometer(endekilometer);

		a.setAvailable(true);
		a.setKilometerstand(endekilometer);
		a.setAktuellerstandort(standortID);

		t.setAvailable(true);
		
		template.saveOrUpdate(f);
		template.saveOrUpdate(a);
		template.saveOrUpdate(t);
	}

	/**
	 * Find-AbgeschlosseneDTO-Methode von FahrtDAO.
	 * <p>
	 * Mittels Zugriff auf die FahrtTeilnehmerDTO können dadurch alle
	 * abgeschlossenen Fahrten angezeigt werden, welche als Basis für die
	 * Rechnungsgenerierung dient.
	 * 
	 * @return {@link Set}<{@link FahrtTeilnehmerDTO}> - Sortierte Liste von FahrtPersonenDTO-Objekten.
	 * @author Martin Schmid
	 * @author Florian Schachner
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Set<FahrtTeilnehmerDTO> findAbgeschlosseneDTO() {
		HibernateTemplate template = getHibernateTemplate();
		return (Set<FahrtTeilnehmerDTO>) template
				.execute(new HibernateCallback() {
					@Override
					public Object doInHibernate(Session session)
							throws HibernateException, SQLException {
						Criteria criteria = session.createCriteria(Fahrt.class)
								.add(Restrictions.isNull("abgerechnet"))
								.add(Restrictions.isNotNull("endefahrt"));

						List<Fahrt> abzurechnendeFahrten = criteria.list();
						return createDtoSet(abzurechnendeFahrten);
					}
				});
	}

	
	/**
	 * Erstellen einer Rechnung für eine spezifische Fahrt.</br>
	 * Diese Methode wird verwendet, um eine Rechnung aus einer beendeten Fahrt zu generieren.
	 * <p>
	 * Diese Mehtode geht wie folgt vor:<br/>
	 * <ul>
	 *  <li>Casten aller benötigten Daten in String-Datentyp</li>
	 * 	<li>Erzeugen einer neuer Instanz eines RechnungDTO-Objekts</li>
	 *  <li>Generieren einer PDF-Rechnung durch übergabe des DTO-Objekts</li>
	 * </ul>
	 * </p>
	 * 
	 * @param fahrt - {@link Fahrt}
	 * @return rechnung - {@link RechnungDTO}
	 * @author Martin Schmid
	 * @author Florian Schachner
	 */
	public RechnungDTO createRechnung(Fahrt fahrt) {
		// Alle nowendigen Daten zu einem String umwandeln
		String strKennzeichen = fahrt.getAutomobil().getKennzeichen();
		String strHersteller = fahrt.getAutomobil().getHersteller();
		String strModell = fahrt.getAutomobil().getModell();
		String strBaujahr = "" + fahrt.getAutomobil().getBaujahr(); // (von Integer zu String)
		String strLeistung = "" + fahrt.getAutomobil().getLeistung(); // (von Integer zu String)
		String strFahrtID = "" + fahrt.getId();
		String strStartFahrt = "" + deutschesDatumsFormat.format(fahrt.getStartfahrt());
		String strStartKilometer = "" + fahrt.getStartkilometer();
		String strEndeFahrt = ""+ deutschesDatumsFormat.format(fahrt.getEndefahrt());
		String strEndeKilometer = "" + fahrt.getEndekilometer();
		String strName = fahrt.getTeilnehmer().getVorname() + " "+ fahrt.getTeilnehmer().getNachname();
		String strAnschriftStr = fahrt.getTeilnehmer().getStrasse();
		String strAnschriftOrt = "" + fahrt.getTeilnehmer().getPlz() + " "	+ fahrt.getTeilnehmer().getOrt();
		String strTelefonnummer = "" + fahrt.getTeilnehmer().getTelefonnummer();

		RechnungDTO rechnung = new RechnungDTO(strKennzeichen, strHersteller,
				strModell, strBaujahr, strLeistung,
				strFahrtID, strStartFahrt, strStartKilometer, strEndeFahrt, strEndeKilometer,
				strName, strAnschriftStr, strAnschriftOrt, strTelefonnummer);

		Rechnung re = new Rechnung(rechnung);
		return rechnung;
	}

	
	/**
	 * Abschließen einer bereits beendeten Fahrt.<br/>
	 * Diese Methode schließt eine Fahrt endgültig ab, 
	 * nachdem eine Rechnung zur Fahrt erzeugt wurde.
	 * <p>
	 * Diese Mehtode geht wie folgt vor:<br/>
	 * <ul>
	 *  <li>Finden einer speziellen Fahrt anhand der FahrtID</li>
	 * 	<li>Setzen des Kennzeichens 'abgerechtet'</li>
	 *  <li>Setzen des Zeitstempels, an dem die Rechnung erzeugt wurde</li>
	 * </ul>
	 * </p>
	 * @param fahrtID {@link Integer}
	 * @author Martin Schmid
	 * @author Florian Schachner
	 */
	public void closeFahrt(int fahrtID) {
		HibernateTemplate template = getHibernateTemplate();
		Fahrt f = (Fahrt) template.get(Fahrt.class, fahrtID);

		f.setAbgerechnet(true);
		f.setCreated(new Date());

		template.saveOrUpdate(f);
	}

	/**
	 * Findet alle Fahrten eines Automobils.<br/>
	 * Diese Methode findet alle absolvierten Fahrten, an denen ein
	 * spezielles Automobil beteiligt war und gibt diese Fahrten zurück.
	 * Wird verwendet, wenn ein Automobil gelöscht werden soll.
	 * 
	 * @param automobil - {@link Automobil}
	 * @return result - {@link Set}<{@link Fahrt}>
	 * @author Martin Schmid
	 * @author Florian Schachner
	 */
	public Set<Fahrt> findByAutomobil(Automobil automobil) {
		Session hibernateSession = getSession();
		Criteria crit = hibernateSession.createCriteria(Fahrt.class);
		crit.add(Restrictions.eq("automobil", automobil));
		List<Fahrt> tmp = crit.list();
		Set<Fahrt> result = new HashSet<Fahrt>();
		for (Fahrt f : tmp) {
			result.add(f);
		}
		return result;
	}
	
	/**
	 * Findet alle Fahrten eines Teilnehmers.<br/>
	 * Diese Methode findet alle absolvierten Fahrten, an denen ein
	 * spezieller Teilnehmer beteiligt war und gibt diese Fahrten zurück.
	 * Wird verwendet, wenn ein Automobil gelöscht werden soll.
	 * 
	 * @param teilnehmer - {@link Teilnehmer}
	 * @return result - {@link Set}<{@link Fahrt}>
	 * @author Martin Schmid
	 * @author Florian Schachner
	 */
	public Set<Fahrt> findByTeilnehmer(Teilnehmer teilnehmer) {
		Session hibernateSession = getSession();
		Criteria crit = hibernateSession.createCriteria(Fahrt.class);
		crit.add(Restrictions.eq("teilnehmer", teilnehmer));
		List<Fahrt> tmp = crit.list();
		Set<Fahrt> result = new HashSet<Fahrt>();
		for (Fahrt f : tmp) {
			log.info(f.getId());
			result.add(f);
		}
		return result;
		
	}
}
